﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management.Automation.Runspaces;
using System.Threading;

namespace BGShell.PowerShell.Runspaces
{
    internal sealed class BGShellPipelineExecutor
    {
        internal static readonly object IsExitPipeline = new object();

        private readonly Runspace _runspace;
        private readonly Boolean _isNested;

        private readonly Object _syncRoot = new Object();
        private readonly Queue<BGShellPipeline> _pipelines = new Queue<BGShellPipeline>();

        private BGShellPipeline _currentPipe;
        private Boolean _exitNestedPrompt;
        
        public BGShellPipelineExecutor(Runspace runspace, Boolean isNested)
        {
            _runspace = runspace;
            _isNested = isNested;
        }

        public void Dispose()
        {
            lock (_syncRoot)
            {
                if (_currentPipe != null)
                {
                    StopPipeline(_currentPipe);
                }

                _currentPipe = null;
                _pipelines.Clear();
            }
        }

        public void Enqueue(BGShellPipeline pipe)
        {
            pipe.OnQueued();

            lock (_syncRoot)
            {
                if (_isNested)
                {
                    _pipelines.Enqueue(pipe);
                    Monitor.Pulse(_syncRoot);
                }
                else
                {
                    pipe.PipelineDisposed += OnPipelineDisposed;

                    if (_currentPipe == null)
                    {
                        Invoke(pipe);
                    }
                    else
                    {
                        _pipelines.Enqueue(pipe);
                    }
                }
            }
        }

        public void StopPipeline(BGShellPipeline pipe)
        {
            lock (_syncRoot)
            {
                pipe.StopAsync();
            }
        }

        public void Run()
        {
            VerifyIsNested();
            _exitNestedPrompt = false;
            
            while (!_exitNestedPrompt)
            {
                lock (_syncRoot)
                {
                    while (_pipelines.Count > 0)
                    {
                        var pipe = _pipelines.Dequeue();

                        Monitor.Exit(_syncRoot);

                        try
                        {
                            Invoke(pipe);
                        }
                        finally
                        {
                            Monitor.Enter(_syncRoot);
                        }
                    }

                    if (!_exitNestedPrompt)
                    {
                        Monitor.Wait(_syncRoot);
                    }
                }
            }
        }

        public void Exit()
        {
            VerifyIsNested();

            lock (_syncRoot)
            {
                Debug.Assert(_currentPipe != null);
                Debug.Assert(_currentPipe.UserState == null);

                // this tells the runspace service not to invoke the prompt function when
                // this pipeline is disposed. the prompt is invoked when the original
                // EnterNestedPrompt pipeline is disposed.
                
                _currentPipe.UserState = IsExitPipeline;

                _exitNestedPrompt = true;
                Monitor.Pulse(_syncRoot);
            }
        }

        private void OnPipelineDisposed(Object sender, EventArgs e)
        {
            var pipe = (sender as BGShellPipeline);

            lock (_syncRoot)
            {
                if (_currentPipe == pipe)
                {
                    _currentPipe = null;

                    while ((_currentPipe == null) && (_pipelines.Count > 0))
                    {
                        Invoke(_pipelines.Dequeue());
                    }
                }
            }
        }

        private void Invoke(BGShellPipeline pipe)
        {
            if (!pipe.IsStopped)
            {
                _currentPipe = pipe;
                _currentPipe.Invoke(_runspace, _isNested);
            }
        }

        private void VerifyIsNested()
        {
            if (!_isNested)
            {
                throw new InvalidOperationException("This method requires a nested BGShellPipelineExecutor.");
            }
        }
    }
}